package jsr292.java.lang.invoke;

import java.lang.reflect.Field;
import java.util.Set;

import jsr292.java.lang.invoke.MethodHandles.Lookup;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerVarAllocator;

public final class BootstrapCallSite extends CallSite {
    private final Lookup lookup;
    private final String name;
    private final MethodHandle bsm;
    private final Object bsmArgs;
    private final String callSiteFieldName;
    
    private BootstrapCallSite(Lookup lookup, String name, MethodType type, MethodHandle bsm, Object bsmArgs, String callSiteFieldName) {
        super(type);
	this.target = new BootstrapInvoker(type);
	this.lookup = lookup;
	this.name = name;
	this.bsm = bsm;
	this.bsmArgs = bsmArgs;
	this.callSiteFieldName = callSiteFieldName;
    }
    
    // called by the backport runtime
    public static BootstrapCallSite create(Lookup lookup, String name, String desc, MethodHandle bsm, String callSiteFieldName) {
        return new BootstrapCallSite(lookup, name, methodType(lookup, desc), bsm, null, callSiteFieldName);
    }
    
    // called by the backport runtime
    public static BootstrapCallSite create(Lookup lookup, String name, String desc, MethodHandle bsm, Object bsmArg, String callSiteFieldName) {
        return new BootstrapCallSite(lookup, name, methodType(lookup, desc), bsm, bsmArg, callSiteFieldName);
    }
    
    // called by the backport runtime
    public static BootstrapCallSite create(Lookup lookup, String name, String desc, MethodHandle bsm, Object[] bsmArgs, String callSiteFieldName) {
	return new BootstrapCallSite(lookup, name, methodType(lookup, desc), bsm, bsmArgs, callSiteFieldName);
    }
    
    // this code can also be found in ConstantMethodHandles
    // I decide to duplicate it to avoid unnecessary class loading
    private static MethodType methodType(Lookup lookup, String desc) {
        return MethodType.fromMethodDescriptorString(desc, lookup.lookupClass().getClassLoader());
    }
    
    
    // called by optimizer runtime, to access to package private method CallSite.invalidateAndGetCallerClass
    public static void invalidateAndPopulateCallerClassSet(CallSite callSite, Set<Class<?>> callerSet) {
        callSite.invalidateAndPopulateCallerClassSet(callerSet);
    }
    
    // called by optimizer runtime, to access to package private CallSite global lock
    public static Object getCallSiteGlobalLock() {
        return CallSite.LOCK;
    }
    
    
    MethodHandle bootstrapCallSite() {
	CallSite callSite;
	try {
	    if (bsmArgs == null) {
	        callSite = (CallSite)bsm.$invoke$(lookup, name, target.type());
	    } else  {
	        if (bsmArgs instanceof Object[]) {
	          Object[] array = (Object[])bsmArgs;
	          array[0] = lookup;
	          array[1] = name;
	          array[2] = target.type();
	          callSite = (CallSite)bsm.invokeWithArguments(array);
	        } else {
	          callSite = (CallSite)bsm.$invoke$(lookup, name, target.type(), bsmArgs);
	        }
	    }
	    
	} catch (Throwable e) {
	    if (e instanceof Error) {
		throw (Error)e;
	    }
	    throw new InvokeDynamicBootstrapError("unexpected exception", e);
	}
	
	if (callSite == null) {
	    throw new InvokeDynamicBootstrapError("call site is null");
	}
	if (callSite.target == null) {
	    throw new InvokeDynamicBootstrapError("call site target is null");
        }
	if (callSite.target.type() != target.type()) {
	    throw new InvokeDynamicBootstrapError("invalid target type "+String.valueOf(target) + " waiting for "+this.target.type());
	}
	
	// try to install the new call site
	Class<?> callerClass = lookup.lookupClass();
	Field field;
	try {
	    field = callerClass.getField(callSiteFieldName);
	} catch (NoSuchFieldException e) {
	    throw new AssertionError(e);
	} catch (SecurityException e) {
	   throw (InvokeDynamicBootstrapError)new InvokeDynamicBootstrapError().initCause(e);
	}
	
	try {
	    field.setAccessible(true);
	} catch (SecurityException e1) {
	    // do nothing, error will be raised when calling field.get
	}
	
	Object existingCallSite;
	try {
	    // use the CallSite global lock
	    synchronized(LOCK) {
	        existingCallSite = field.get(null);
	        if (existingCallSite == this) {
	            field.set(null, callSite);

	            // inject caller class, LOCK must be held
	            callSite.injectCallerClass(callerClass);
	            return callSite.target;
	        }
	    }
	} catch (IllegalArgumentException e) {
	    throw new AssertionError(e);
	} catch (IllegalAccessException e) {
	    throw (InvokeDynamicBootstrapError)new InvokeDynamicBootstrapError().initCause(e);
	}
	
	// returns already registered callsite target
	return ((CallSite)existingCallSite).target;
    }
    
    private class BootstrapInvoker extends MethodHandle {
        BootstrapInvoker(MethodType methodType) {
	    super(methodType);
	}

	@Override
	public Object $invoke$() throws Throwable {
	    return bootstrapCallSite().$invoke$();
	}
	@Override
	public Object $invoke$(Object o1) throws Throwable {
	    return bootstrapCallSite().$invoke$(o1);
	}
	@Override
	public Object $invoke$(Object o1, Object o2) throws Throwable {
	    return bootstrapCallSite().$invoke$(o1, o2);
	}
	@Override
	public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
	    return bootstrapCallSite().$invoke$(o1, o2, o3);
	}
	@Override
	public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
	    return bootstrapCallSite().$invoke$(o1, o2, o3, o4);
	}
	@Override
	public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
	    return bootstrapCallSite().$invoke$(o1, o2, o3, o4, o5);
	}
	@Override
	public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
	    return bootstrapCallSite().$invoke$(o1, o2, o3, o4, o5, o6);
	}
	@Override
	public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
	    return bootstrapCallSite().$invoke$(o1, o2, o3, o4, o5, o6, o7);
	}
	@Override
	public Object $invoke$(Object... rest) throws Throwable {
	    return bootstrapCallSite().$invoke$(rest);
	}
	
	@Override
	public boolean isWeavable(Class<?> classBeingRedefined) {
	    throw super.assertion();
	}
	@Override
	public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
	    throw super.fail(); 
	}
    }

    @Override
    public MethodHandle getTarget() {
        throw new UnsupportedOperationException();
    }
    @Override
    public void setTarget(MethodHandle target) {
        throw new UnsupportedOperationException();
    }
    @Override
    public MethodHandle dynamicInvoker() {
        throw new UnsupportedOperationException();
    }
}
